import os
from datetime import datetime
from random import choice
from typing import List, Optional

import aiofiles
import aiohttp
import pyuseragents
from aiogram import Bot
from aiogram.types import BufferedInputFile
from aiohttp import ClientHttpProxyError, ClientProxyConnectionError
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from sqlalchemy.orm import sessionmaker

from app.bot.utils.broadcaster import broadcast, broadcast_document
from app.bot.utils.date_time import get_date_time
from app.bot.utils.kopeechka.errors import BAD_TOKEN
from app.bot.utils.logger import log
from app.bot.utils.wlo_reger import (
    CaptchaNotSolvedException,
    NoEmailException,
    WLOReger,
)
from app.config import Settings
from app.database.functions.account import (
    delete_user_accounts,
    get_selfie_accounts,
    get_user_accounts,
    update_account,
    update_id_account,
)
from app.database.functions.document import delete_user_docs, get_user_docs
from app.database.functions.tasks import delete_user_tasks, get_user_tasks
from app.database.functions.unique import delete_user_creatives, get_user_creatives
from app.database.functions.whitepages import (
    delete_user_whitepages,
    get_user_whitepages,
)
from app.database.models import User

headers = {"user-agent": pyuseragents.random()}


async def get_url(url, headers_dict: dict):
    async with aiohttp.ClientSession(headers=headers_dict) as session:
        async with session.get(url, verify_ssl=False, allow_redirects=False) as req_:
            return req_.headers


async def get_user_jobs(
    scheduler: AsyncIOScheduler, telegram_id: int = None, job_type: str = None
) -> list:
    """
    Получаем список задач по id юзера
    """

    log.info(f"Получаю задачи для юзера {telegram_id}")
    if telegram_id:
        user_jobs = [
            job
            for job in scheduler.get_jobs()
            if telegram_id in job.args and job.name == job_type
        ]
    else:
        user_jobs = [job for job in scheduler.get_jobs() if job.name == job_type]
    return user_jobs


async def remove_jobs(
    scheduler: AsyncIOScheduler,
    user_jobs: list,
    time_now: datetime = None,
    time_limit: datetime = None,
) -> list:
    """
    Удаляем задачи юзера
    """

    for job in user_jobs:
        scheduler.remove_job(job.id)
        if time_now is not None and time_now >= time_limit:
            log.info(f"Задача {job.name} удалена (превышено время исполнения)")
        else:
            log.info(f"Задача {job.name} удалена")
        user_jobs.remove(job)
    return user_jobs


async def notify_user(
    bot: Bot,
    session_factory: sessionmaker,
    scheduler: AsyncIOScheduler,
    telegram_id: int,
    time_limit: datetime,
) -> None:
    """
    Проверяем задачу по разбанам
    """

    time_now = get_date_time(get_datetime=True)
    async with session_factory() as session:
        accounts = await get_user_accounts(session, telegram_id)
        accounts_statuses = [account.result_mes for account in accounts]

        # Если нет аккаунтов без статуса, отправляем сообщение и снимаем задачу
        if all(accounts_statuses) or time_now >= time_limit:
            log.info(
                f"Лимит на выполнение задачи: {time_limit}. Текущее время: {time_now}"
            )
            txt = ""
            otrabotano = [account for account in accounts if account.result_mes]
            accs_to_check = []
            for account in otrabotano:
                if account.status in (
                    "zagruzil doki",
                    "zagruzil selfy",
                ):
                    accs_to_check.append(account)
                else:
                    txt += f"{account.login} :: {account.token} :: {account.useragent} :: :: {account.Cookies_multilogin} :: {account.password}|{account.email} - {account.result_mes}\n"

            user_jobs = await get_user_jobs(scheduler, telegram_id, job_type="Accounts")
            jobs = await remove_jobs(scheduler, user_jobs, time_now, time_limit)

            if len(jobs) == 0:
                txt = bytes(txt, "utf-8")
                if len(txt) > 0:
                    await broadcast_document(
                        bot, telegram_id, BufferedInputFile(txt, "accounts.txt")
                    )
                else:
                    log.error(f"Не могу отправить пустой файл. Длина файла: {len(txt)}")
                caption = (
                    f"Принято в работу аккаунтов: {len(accounts_statuses)}\n"
                    f"Отработано: {len(otrabotano)-len(accs_to_check)}\n"
                )
                if len(accs_to_check) > 0:
                    caption += f"На дополнительной проверке: {len(accs_to_check)}\n"
                for account in otrabotano:
                    caption += f"\n{account.login} - {account.result_mes}"
                results = bytes(caption, "utf-8")
                if len(txt) > 0:
                    await broadcast_document(
                        bot, telegram_id, BufferedInputFile(results, "results.txt")
                    )
                else:
                    log.error(
                        f"Не могу отправить пустой файл. Длина файла: {len(results)}"
                    )
                await delete_user_accounts(session, telegram_id)


async def check_tasks(
    bot: Bot,
    session_factory: sessionmaker,
    scheduler: AsyncIOScheduler,
    telegram_id: int,
) -> None:
    """
    Проверяем задачу по лайкам/комментариям
    """

    async with session_factory() as session:
        tasks = await get_user_tasks(session, telegram_id)
        task_statuses = [task.status for task in tasks]
        if "busy" not in task_statuses:
            log.info(
                f"Проверяю задачу(и) по лайкам/комментариям для пользователя {telegram_id}"
            )
            txt = "Статистика по вашим задачам в формате: ссылка - количество выполненных действий (лайки/комментарии)\n\n"
            for task in tasks:
                txt += f"{task.link} - {task.count_done}\n"

            user_jobs = await get_user_jobs(
                scheduler, telegram_id, job_type="Likes/comments"
            )
            jobs = await remove_jobs(scheduler, user_jobs)

            if len(jobs) == 0:
                txt = bytes(txt, "utf-8")
                if len(txt) > 0:
                    await broadcast_document(
                        bot, telegram_id, BufferedInputFile(txt, "results.txt")
                    )
                else:
                    log.error(f"Не могу отправить пустой файл. Длина файла: {len(txt)}")
                await delete_user_tasks(session, telegram_id)


async def check_docs(
    bot: Bot,
    session_factory: sessionmaker,
    scheduler: AsyncIOScheduler,
    telegram_id: int,
) -> None:
    """
    Проверка сгенерированных документов
    """

    async with session_factory() as session:
        docs = await get_user_docs(session, telegram_id)
        docs_statuses = [doc.status for doc in docs]
        if "busy" not in docs_statuses:
            log.info(f"Проверяю документ(ы) для пользователя {telegram_id}")

            files = [doc.img for doc in docs]
            user_jobs = await get_user_jobs(
                scheduler, telegram_id, job_type="Documents"
            )
            jobs = await remove_jobs(scheduler, user_jobs)

            if len(jobs) == 0:
                for file in files:
                    if file and len(file) > 1:
                        try:
                            async with aiofiles.open(
                                f"app/bot/images/{file}", "rb"
                            ) as f:
                                file_content = await f.read()
                            await broadcast_document(
                                bot,
                                telegram_id,
                                BufferedInputFile(
                                    file_content, "Водительские права укр.jpg"
                                ),
                            )
                        except FileNotFoundError:
                            log.error(f"Файл {file} не найден")
                    else:
                        log.error(
                            f"Не могу отправить пустой документ. Длина файла: {len(file)}"
                        )
                    if os.path.exists(f"app/bot/images/{file}"):
                        os.remove(f"app/bot/images/{file}")
                await delete_user_docs(session, telegram_id)


async def check_accounts(
    bot: Bot,
    session_factory: sessionmaker,
    scheduler: AsyncIOScheduler,
    telegram_id: int = None,
    accounts: Optional[List[str]] = None,
    time_limit: datetime = None,
) -> None:
    """
    Проверка аккаунтов с статусом загрузил доки и загрузил селфи запросом к фб
    accounts: List[str] - список аккаунтов, загруженный самим пользователем
    """

    time_now = get_date_time(get_datetime=True)
    async with session_factory() as session:
        if not accounts:
            accs_to_check = await get_selfie_accounts(session)
        else:
            accs_to_check = accounts

        if len(accs_to_check) > 0:
            log.info(
                f"Проверяю аккаунты аккаунты по ID. Количество аккаунтов: {len(accs_to_check)}"
            )
            log.info(
                f"Лимит на выполнение задачи: {time_limit}. Текущее время: {time_now}"
            )
            for account in accs_to_check:
                if type(account) != str and account.useragent:
                    headers = {"user-agent": account.useragent}
                    url = (
                        f"http://graph.facebook.com/{account.ID_akk}/picture?type=large"
                    )
                else:
                    url = f"http://graph.facebook.com/{account}/picture?type=large"

                req_ = await get_url(url, headers_dict=headers)
                check = req_.get("Location")
                if check != "" and ".gif" not in check:
                    if not accounts:
                        await update_account(
                            session,
                            result="'Разбанен'",
                            account_id=int(account.ID_akk),
                        )
                        await broadcast(
                            bot,
                            [account.user.telegram_id],
                            f"Ваш аккаунт {account.login} разбанен",
                        )
                        accs_to_check.remove(account)
                    else:
                        await update_id_account(
                            session,
                            result="'Разбанен'",
                            account_id=account,
                        )
                        await broadcast(
                            bot, [telegram_id], f"Ваш аккаунт {account} разбанен"
                        )
                        accs_to_check.remove(account)

                elif ".gif" in check:
                    if not accounts:
                        await update_account(
                            session,
                            result="'Забанен'",
                            account_id=int(account.ID_akk),
                        )
                        await broadcast(
                            bot,
                            [account.user.telegram_id],
                            f"Ваш аккаунт {account.login} забанен навсегда",
                        )
                        accs_to_check.remove(account)
                    else:
                        await update_id_account(
                            session,
                            result="'Забанен'",
                            account_id=account,
                        )
                        if time_now >= time_limit:

                            await broadcast(
                                bot,
                                [telegram_id],
                                f"Ваш аккаунт {account.login} забанен навсегда",
                            )

                            accs_to_check.remove(account)

            if telegram_id and len(accs_to_check) == 0:
                user_jobs = await get_user_jobs(
                    scheduler, telegram_id, job_type="Accounts by id"
                )
                if time_limit:
                    jobs = await remove_jobs(scheduler, user_jobs, time_now, time_limit)

                if len(jobs) == 0:
                    log.info(
                        f"Снял задачу по проверке аккаунтов по id для юзера {telegram_id}"
                    )


async def check_uniques(
    bot: Bot,
    session_factory: sessionmaker,
    scheduler: AsyncIOScheduler,
    telegram_id: int,
) -> None:
    """
    Проверка уникализации фото/видео
    """

    async with session_factory() as session:
        creatives = await get_user_creatives(session, telegram_id)
        creatives_statuses = [creative.status for creative in creatives]

        if "busy" not in creatives_statuses:
            log.info(f"Проверяю креатив(ы) для пользователя {telegram_id}")
            done_files = [creative.file_path for creative in creatives]
            user_jobs = await get_user_jobs(
                scheduler, telegram_id, job_type="Unique photo/video"
            )
            jobs = await remove_jobs(scheduler, user_jobs)

            if len(jobs) == 0:
                for file_name in done_files:
                    if file_name and len(file_name) > 1:
                        try:
                            async with aiofiles.open(
                                f"app/bot/images/{file_name}", "rb"
                            ) as f:
                                file_content = await f.read()
                            await broadcast_document(
                                bot,
                                telegram_id,
                                BufferedInputFile(file_content, file_name),
                                disable_content_type_detection=True,
                            )

                        except FileNotFoundError:
                            log.error(f"Файл {file_name} не найден")
                    else:
                        log.error(
                            f"Не могу отправить пустой файл. Длина файла: {len(file_name)}"
                        )
                    if os.path.exists(f"app/bot/images/{file_name}"):
                        os.remove(f"app/bot/images/{file_name}")
                await delete_user_creatives(session, telegram_id)


async def check_whites(
    bot: Bot,
    session_factory: sessionmaker,
    scheduler: AsyncIOScheduler,
    telegram_id: int,
) -> None:
    """
    Проверяем вайтпейджи
    """

    async with session_factory() as session:
        whitepages = await get_user_whitepages(session, telegram_id)
        whitepages_statuses = [whitepage.status for whitepage in whitepages]

        if "busy" not in whitepages_statuses:
            log.info(
                f"Проверяю задачу(и) по генерации вайта(ов) для пользователя {telegram_id}"
            )
            txt = "Сгенерированные вайты в формате: ссылка на вайт - логин - пароль\n\n"
            for whitepage in whitepages:
                txt += f"{whitepage.white_link} - {whitepage.white_login} - {whitepage.white_pass}\n"

            user_jobs = await get_user_jobs(
                scheduler, telegram_id, job_type="Whitepages"
            )
            jobs = await remove_jobs(scheduler, user_jobs)

            if len(jobs) == 0:
                txt = bytes(txt, "utf-8")
                if len(txt) > 0:
                    await broadcast_document(
                        bot, telegram_id, BufferedInputFile(txt, "whitepages.txt")
                    )
                else:
                    log.error(f"Не могу отправить пустой файл. Длина файла: {len(txt)}")
                await delete_user_whitepages(session, telegram_id)


async def generate_wlo_accs(
    bot: Bot,
    config: Settings,
    accounts_amount: int,
    user: User,
) -> None:
    """
    Генерируем аккаунты wlo
    """

    txt = ""
    success = 0

    log.info(
        f"Генерирую {accounts_amount} аккаунт(ов) WLO по запросу юзера {user.telegram_id}"
    )

    for i in range(1, accounts_amount + 1):
        wlo = WLOReger(user.capmonster_token, user.kopeechka_token)

        if user.proxies:
            proxies = user.proxies.split("\n")
            proxy_string = choice(proxies)
        else:
            proxy_string = None

        try:
            email, password, is_verified = await wlo.register_account(
                user.telegram_id, proxy_string
            )
            if email and password:
                success += 1
                if is_verified:
                    txt += f"{i}. {email}:{password}\n – Почта подтверждена"
                else:
                    txt += f"{i}. {email}:{password} – не удалось подтвердить почту\n"

        except BAD_TOKEN:
            await broadcast(
                bot,
                [user.telegram_id],
                "Неверный токен копеечки. Проверьте правильность токена или используйте временную почту",
            )
            break
        except CaptchaNotSolvedException:
            txt += f"{i}. Не удалось решить капчу\n"
        except (ClientProxyConnectionError, ClientHttpProxyError, TimeoutError) as e:
            txt += f"{i}. Не могу подключиться к прокси {proxy_string}\n"
        except NoEmailException:
            txt += f"{i}. Не удалось получить почту для регистрации\n"
        except Exception as e:
            log.exception(
                f"Непредвиденная ошибка во время регистрации аккаунта WLO: {str(e)}"
            )
        finally:
            await wlo.session.close()

    if len(txt) > 0:
        txt = "Отчет о регистрации WLO\n\n" + txt
        if len(txt) > 4096:
            await broadcast_document(
                bot,
                user.telegram_id,
                BufferedInputFile(bytes(txt, "utf-8"), "Отчет о регистрации WLO.txt"),
            )
        else:
            await broadcast(bot, [user.telegram_id], txt)
        log.info(
            f"Успешно сгенерировано: {success} из {accounts_amount} аккаунтов для юзера {user.telegram_id}"
        )
    else:
        await broadcast(
            bot,
            [user.telegram_id],
            "Не удалось сгенерировать заданное вами число аккаунтов",
        )
        await broadcast(
            bot, [config.tg_bot.developer_id], "Ошибка во время генерации аккаунтов WLO"
        )
        log.error(
            f"Не удалось сгенерировать {accounts_amount} аккаунт(ов) WLO для юзера {user.telegram_id}"
        )
